package modbus

import (
	"MqttCloudProtocolServer/ProtocolServer/mqttlib"
	"MqttCloudProtocolServer/publiclib/datamodel/dborm"
	"MqttCloudProtocolServer/publiclib/dbcontrol"
	"MqttCloudProtocolServer/publiclib/helplib"
	"errors"
	"strconv"
	"time"

	"gitee.com/lossage/goProtocols/modbus"
	"github.com/wonderivan/logger"
)

type ModbusAnalysis struct {
	mqttlib.OperatorBase
	lastFrame          *modbus.ModbusProtocol
	analysis           *ModbusDataAnalysis
	mqttSender         func(key string, buffer []uint8)
	dbtcp              *dborm.Manageserver
	cmds               []*dborm.Modbuscommands
	isRunning          bool
	scanRate           int
	readTimeOut        int
	readBufferWaitChan chan []uint8
	writeReturnResChan chan bool
	willWriteChan      chan *modbus.ModbusProtocol
	//locker             sync.Mutex
	unpackLinkLayer func(buffer []uint8) []uint8 //解包链路层数据
	packLinkLayer   func(buffer []uint8) []uint8 //打包链路层数据
	transIDAdd      func()
}

func (_this *ModbusAnalysis) ReadBufferFromExt(timeout int) []uint8 {
	if res, flag := helplib.ReadChannelInTimeMS(&_this.readBufferWaitChan, timeout); flag {
		return *res
	} else {
		return nil
	}
}
func (_this *ModbusAnalysis) ClearReadBuffer() {
	//_this.locker.Lock()
	//defer _this.locker.Unlock()
	helplib.ClearChannelInTimeMS(&_this.readBufferWaitChan, 1)
}
func (_this *ModbusAnalysis) Start() bool {
	if _this.dbtcp == nil {
		return false
	}
	if len(_this.cmds) == 0 {
		return true
	}
	_this.scanRate = 1000
	if paramInt, err := strconv.Atoi(_this.dbtcp.Param1); err == nil {
		_this.scanRate = paramInt
	}
	_this.readTimeOut = 1000
	if paramInt, err := strconv.Atoi(_this.dbtcp.Param2); err == nil {
		_this.readTimeOut = paramInt
	}
	_this.willWriteChan = make(chan *modbus.ModbusProtocol, 1)
	_this.readBufferWaitChan = make(chan []uint8, 1)
	_this.writeReturnResChan = make(chan bool, 1)
	run := func() {
		for {
			//创建一个字典，初始值为-1，主键为地址。循环中，正确：-1->0,1->2,0>0,2->2,异常：-1->1,0->2,1->1,2->2
			comFlagDic := make(map[int]int8)
			for _, cmd := range _this.cmds {
				if !_this.isRunning {
					return
				}
				//检查通讯状态初始情况
				if _, ok := comFlagDic[cmd.Addr]; !ok {
					comFlagDic[cmd.Addr] = -1
				}

				//有控制命令，则复制写发送帧，进行发送
				if len(_this.willWriteChan) > 0 {
					if res, flag := helplib.ReadChannelInTimeMS(&_this.willWriteChan, 1); flag {
						tempFrame := *res
						if tempFrame != nil {
							_this.lastFrame = tempFrame
							if err := _this.writeCheck(tempFrame); err != nil { //写入后，检查有错误
								logger.Alert(err.Error() + ":" + cmd.UUID)
								_this.writeReturnResChan <- false
							} else { //写入后，回应检查正确
								_this.writeReturnResChan <- true
								time.Sleep(time.Duration(_this.scanRate) * time.Millisecond)
							}
						}
					}
				}
				//发送完成
				//写命令完成，继续读取。线程内发送与等待回应数据
				_this.ClearReadBuffer()
				if tempFrame, err := _this.SendReadOnce(uint8(cmd.Addr), uint8(cmd.Command), uint16(cmd.RegStart), uint8(cmd.RegCount)); err != nil {
					logger.Alert(cmd.UUID + ":发送出错：" + err.Error())
					continue
				} else {
					_this.lastFrame = tempFrame
				}
				tempStart := time.Now()
				for {
					spanMil := (time.Now().UnixMilli() - tempStart.UnixMilli())
					if int64(_this.readTimeOut) > spanMil {
						if readBuffer := _this.ReadBufferFromExt(_this.readTimeOut - int(spanMil)); readBuffer != nil {
							resFrame := _this.readbytesToModbus(readBuffer)
							if resFrame != nil {
								//读取及报文均正常
								_this.updateData(cmd, resFrame)
								comFlagDic[cmd.Addr] = ComCheck(comFlagDic[cmd.Addr], true)
								break
							} else {
								//读取到报文，但报文帧错误，通讯状态判断
								comFlagDic[cmd.Addr] = ComCheck(comFlagDic[cmd.Addr], false)
								continue
							}
						} else {
							//读取超时，通讯状态判断
							logger.Info(cmd.UUID + ":单次读取超时")
							comFlagDic[cmd.Addr] = ComCheck(comFlagDic[cmd.Addr], false)
							break
						}
					} else {
						//读取超时，通讯状态判断
						logger.Info(cmd.UUID + ":读取循环等待超时")
						comFlagDic[cmd.Addr] = ComCheck(comFlagDic[cmd.Addr], false)
						break
					}
				}
				//读取完成
				time.Sleep(time.Duration(_this.scanRate) * time.Millisecond)
			}
			//命令执行完了，更新通讯状态
			for key, value := range comFlagDic {
				_this.updateComflag(_this.dbtcp.UUID, key, value)
			}
		}
	}
	_this.isRunning = true
	time.Sleep(time.Second * 3) //等待启动完成
	go run()
	return true
}
func ComCheck(org int8, statue bool) int8 {
	//正确：-1->0,1->2,0>0,2->2,异常：-1->1,0->2,1->1,2->2
	if statue {
		switch org {
		case -1:
			return 0
		case 1:
			return 2
		case 0:
			return 0
		case 2:
			return 2
		default:
			return org
		}
	} else {
		switch org {
		case -1:
			return 1
		case 0:
			return 2
		case 1:
			return 1
		case 2:
			return 2
		default:
			return org
		}
	}
}
func (_this *ModbusAnalysis) updateComflag(device string, addr int, flag int8) {
	//_this.comFlag 更新到Redis
	//0:正常 -1:未初始化 1:通讯有错误
	comDis := GetComDis(flag)
	UpdateComFlag(device, addr, flag, comDis)
}

func GetComDis(flag int8) string {
	comDis := ""
	switch flag {
	case 0:
		comDis = "正常"
	case -1:
		comDis = "未初始化"
	case 1:
		comDis = "通讯有错误"
	case 2:
		comDis = "通讯部分错误"
	default:
		comDis = "未知通讯状态"
	}
	return comDis
}
func (_this *ModbusAnalysis) updateData(cmd *dborm.Modbuscommands, frame *modbus.ModbusProtocol) {
	//更新接收帧到Redis中
	if frame == nil || cmd == nil {
		logger.Alert(cmd.UUID + ":收到的报文帧或命令错误")
		return
	}
	_this.analysis.AnalysisData(cmd, frame)
}

// Modify Fun 外部线程调用，但属于初始化部分（启动前）
func (_this *ModbusAnalysis) Modify(params interface{}) bool {
	if value, ok := params.(*dborm.Manageserver); ok {
		_this.dbtcp = value
		cmdControl := dbcontrol.CreateModbusCommandsControl()
		if cmdControl == nil {
			logger.Error("数据库读取失败")
			return false
		}
		cmds, err := cmdControl.GetByUUID(value.UUID)
		if err != nil {
			logger.Error("获取命令失败")
			return false
		}
		_this.cmds = make([]*dborm.Modbuscommands, len(cmds))
		i := 0
		for _, cmd := range cmds {
			if cmd.Addr < 1 || cmd.Addr > 254 {
				continue
			}
			if cmd.Command < 1 || cmd.Command > 254 {
				continue
			}
			if cmd.RegStart < 0 || cmd.RegStart > 65535 {
				continue
			}
			if cmd.RegCount < 1 || cmd.RegCount > 254 {
				continue
			}
			_this.cmds[i] = cmd
			i++
		}
		_this.cmds = _this.cmds[:i]
		_this.analysis = new(ModbusDataAnalysis)
		_this.analysis.Init(_this.cmds, _this.writeCallback)
		return true
	}
	return false
}

// 数据分析后调用。存在于Redis收到控制命令时，由Redis调用
// 这是外部线程控制的
func (_this *ModbusAnalysis) writeCallback(tagPoint *TagForControl) bool {
	//生成控制/写入帧，然后需要在循环线程中处理发送
	//注意线程同步
	//写入信息应交给同步线程去处理
	tempFrame := new(modbus.ModbusProtocol)
	tempFrame.Addr = tagPoint.Addr
	tempFrame.Command = tagPoint.CmdFunc
	tempFrame.RegNo = tagPoint.RegNo
	tempFrame.RegLength = uint8(len(tagPoint.Value))
	tempFrame.Body = tagPoint.Value
	helplib.ClearChannelInTimeMS(&_this.willWriteChan, 1)
	_this.willWriteChan <- tempFrame

	//等待写的消息发出去之后，等待返回结果即可
	select {
	case writeTemp := <-_this.writeReturnResChan:
		return writeTemp
	case <-time.After(time.Millisecond * time.Duration(_this.scanRate+_this.readTimeOut+100)): //控制命令等待扫描周期加读取超时时间，以免正在读取，错过了时序
		return false
	}
}

// 检查是否有需要写入的信息，由线程循环调用
func (_this *ModbusAnalysis) writeCheck(sendFrame *modbus.ModbusProtocol) error {
	if sendFrame == nil {
		return errors.New("没有发送帧")
	}

	sendbuffer, err := sendFrame.AnalyzeStruct()
	if err != nil {
		return err
	} else if _this.mqttSender == nil {
		return errors.New("没有提供发送函数")
	} else if sendbuffer == nil || len(sendbuffer) <= 0 {
		return errors.New("发送数据为空")
	} else {
		_this.ClearReadBuffer()
		if _this.packLinkLayer != nil {
			sendbuffer = _this.packLinkLayer(sendbuffer)
		}
		_this.mqttSender(_this.dbtcp.UUID, sendbuffer)
		if _this.transIDAdd != nil {
			_this.transIDAdd()
		}
	}
	tempStart := time.Now()
	for {
		spanMil := (time.Now().UnixMilli() - tempStart.UnixMilli())
		if int64(_this.readTimeOut) > spanMil {
			if buffer := _this.ReadBufferFromExt(_this.readTimeOut - int(spanMil)); buffer != nil {
				receiveTemp := _this.readbytesToModbus(buffer)
				if receiveTemp == nil {
					logger.Info("控制命令返回校验错误")
					continue
					//return errors.New("控制命令返回校验错误")
				}
				if receiveTemp.Addr == sendFrame.Addr &&
					receiveTemp.Command == sendFrame.Command &&
					receiveTemp.RegNo == sendFrame.RegNo {
					return nil
				} else {
					logger.Info("控制命令返回校验错误")
					continue
					//return errors.New("控制命令返回校验错误")
				}
			} else {
				//读取超时
				return errors.New("控制命令超时")
			}
		}
	}
}

// Close Fun 这是外部线程控制的
func (_this *ModbusAnalysis) Close() bool {
	_this.isRunning = false
	close(_this.willWriteChan)
	close(_this.readBufferWaitChan)
	close(_this.writeReturnResChan)
	return true
}

// MQTT收到数据，发送到这里,发送到线程同步器
// 这是另一个线程控制的
func (_this *ModbusAnalysis) SendData(buffer []uint8) bool {
	_this.ClearReadBuffer()
	_this.readBufferWaitChan <- buffer
	return true
}

// 只在同步线程中操作。因此不需要锁
func (_this *ModbusAnalysis) readbytesToModbus(buffer []uint8) *modbus.ModbusProtocol {
	var receivwBuffer []uint8 = nil
	if _this.unpackLinkLayer != nil {
		receivwBuffer = _this.unpackLinkLayer(buffer)
	}
	if receivwBuffer == nil {
		logger.Info("crc校验失败")
		return nil
	}

	tempReceiveFrame := new(modbus.ModbusProtocol)

	tempFrame := _this.lastFrame
	tempReceiveFrame.RegLength = tempFrame.RegLength
	err := tempReceiveFrame.AnalyzeBytes(receivwBuffer, modbus.Receive)

	if err != nil {
		// logger.Info(err.Error())
		return nil
	}
	if tempReceiveFrame.Addr != tempFrame.Addr ||
		tempReceiveFrame.Command != tempFrame.Command ||
		len(tempReceiveFrame.Body) != int(tempFrame.RegLength) {
		switch tempReceiveFrame.Command {
		case modbus.ForceMultipleCoils, modbus.ForceSingleCoil, modbus.PresetMultipleRegisters, modbus.PresetSingleRegister:
			if tempReceiveFrame.RegLength != tempFrame.RegLength {
				return nil
			}
		default:
			if (tempReceiveFrame.RegNo != tempFrame.RegNo) || (len(tempReceiveFrame.Body) != int(tempFrame.RegLength)) {
				return nil
			}
		}
	}
	return tempReceiveFrame
}

// 这里的callback是MQTT的发送函数
// 这是另一个外部线程控制的
func (_this *ModbusAnalysis) SetReceive(callback func(key string, buffer []uint8)) {
	_this.mqttSender = callback
}

// Delete Fun 这是外部线程控制的
func (_this *ModbusAnalysis) Delete() bool { return true }

// 只在同步线程中操作，因此不需要锁
func (_this *ModbusAnalysis) SendReadOnce(addr, command uint8, regNo uint16, regLength uint8) (*modbus.ModbusProtocol, error) {
	tempFrame := new(modbus.ModbusProtocol)
	tempFrame.Addr = addr
	tempFrame.Command = modbus.CommandType(command)
	tempFrame.RegNo = regNo
	tempFrame.RegLength = regLength
	sendbuffer, err := tempFrame.AnalyzeStruct()
	if err != nil {
		return nil, err
	} else if _this.mqttSender == nil {
		return nil, errors.New("没有提供发送函数")
	} else if sendbuffer == nil || len(sendbuffer) <= 0 {
		return nil, errors.New("发送数据为空")
	} else {

		if _this.packLinkLayer != nil {
			sendbuffer = _this.packLinkLayer(sendbuffer)
		}
		_this.mqttSender(_this.dbtcp.UUID, sendbuffer)
		if _this.transIDAdd != nil {
			_this.transIDAdd()
		}
	}
	return tempFrame, nil
}
